<template>
	<view
	:class="'iframe' + dataId"
	class="find_iframe"
	:data-sandbox="sandbox"
	:data-allow="allow"
	:data-allowfullscreen="allowfullscreen"
	:data-frameborder="frameborder"
	:data-loadingShow="loadingShow"
	:data-console="console"
	:data-iframeStyle="iframeStyleString"
	:data-iframeClass="iframeClassString"
	:data-crossOrigin="crossOrigin"
	:data-setDataName="setDataName"
	:data-overrideUrlLoadingOptions="overrideUrlLoadingOptionsString"
	:ready="ready" :change:ready="ComIframe.readyWatcher"
	:isDestroy="isDestroy" :change:isDestroy="ComIframe.destroyWatcher"
	:src="src" :change:src="ComIframe.srcWatcher"
	:sandbox="sandbox" :change:sandbox="ComIframe.sandboxWatcher"
	:allow="allow" :change:allow="ComIframe.allowWatcher"
	:allowfullscreen="allowfullscreen" :change:allowfullscreen="ComIframe.allowfullscreenWatcher"
	:frameborder="frameborder" :change:frameborder="ComIframe.frameborderWatcher"
	:iframeStyle="iframeStyle" :change:iframeStyle="ComIframe.iframeStyleWatcher"
	:iframeClass="iframeClass" :change:iframeClass="ComIframe.iframeClassWatcher"
	:jump="jump" :change:jump="ComIframe.jumpWatcher"
	:assignUrl="assignUrl" :change:assignUrl="ComIframe.assignUrlWatcher"
	:loadingStatus="loadingStatus" :change:loadingStatus="ComIframe.loadingWatcher"
	:evalJS="evalJSString" :change:evalJS="ComIframe.evalJSWatcher"
	:evalCSS="evalCSSString" :change:evalCSS="ComIframe.evalCSSWatcher"
	:setData="setDataValue" :change:setData="ComIframe.setDataWatcher"
	:postMessage="postMessageValue" :change:postMessage="ComIframe.postMessageWatcher"
	:isReload="isReload" :change:isReload="ComIframe.reloadWatcher"
	:overrideUrlLoading="overrideUrlLoadingOptionsString" :change:overrideUrlLoading="ComIframe.overrideUrlLoadingWatcher">
	</view>
</template>

<script>
	export default {
		props: {
			dataId: {
				type: String,
				default () {
					return new Date().getTime().toString() + Math.round(Math.random() * 10000)
				}
			},
			src: {//链接
				type: String,
				default: ''
			},
			sandbox: {//沙盒模式 原生属性
				type: String,
				default: 'allow-same-origin allow-top-navigation allow-forms allow-scripts allow-modals allow-downloads allow-popups'
			},
			allow: {//允许一些操作 原生属性
				type: String,
				// default: 'autoplay; fullscreen; encrypted-media; picture-in-picture'
				default: ''
			},
			//跨域属性 anonymous-它有一个默认值。它定义了将在不传递凭据信息的情况下发送CORS请求 use-credentials-将发送带有凭据、cookie 和证书的 cross-origin 请求
			crossOrigin: {
				type: String,
				default: ''
			},
			allowfullscreen: {//是否允许全屏 原生属性
				type: Boolean,
				default: true
			},
			frameborder: {//iframe边框 原生属性
				type: String,
				default: '0'
			},
			iframeClass: {//iframe样式
				type: [String, Object],
				default: ''
			},
			iframeStyle: {//iframe样式
				type: [String, Object],
				default: ''
			},
			loadingShow: {//展示loading
				type: Boolean,
				default: true
			},
			console: {//控制console
				type: String,
				default: 'log warn error'
			},
			overrideUrlLoadingOptions: {//仅支持app
				type: Object,
				default: () => {
					return {
						effect: '',//拦截URL请求生效时机 instant-立即生效 touchstart-用户操作Webview窗口后生效
						mode: '',//拦截模式 allow-不满足match属性定义的条件时拦截url跳转 reject-满足match属性定义的提交时拦截url跳转
						match: '',//区配是否需要处理的URL请求 支持正则表达式，默认值为对所有URL地址生效（相当于正则表达式“.*”）
						exclude: ''//排除拦截处理请求类型 none-不排除任何URL请求（即拦截处理所有URL请求） redirect-排除拦截处理301/302跳转的请求（谨慎使用，非a标签的href触发的URL请求可能会误判断为302跳转）
					}
				}
			}
		},
		computed: {
			overrideUrlLoadingOptionsString () {
				return JSON.stringify(this.overrideUrlLoadingOptions)
			},
			iframeStyleString () {
				return JSON.stringify(this.iframeStyle)
			},
			iframeClassString () {
				return JSON.stringify(this.iframeClass)
			}
		},
		data () {
			return {
				ready: '',
				jump: 0,
				isReload: -1,//是否刷新
				isDestroy: false,
				evalJSString: '',//js字符串
				evalCSSString: '',//注入css字符串
				assignUrl: '',//跳转链接
				loadingStatus: -1,//加载进度提控制
				postMessageValue: '',//消息通知数据存储
				evalJSTask: new Map(),//注入js临时队列表
				setDataName: '',
				setDataValue: '',
			}
		},
		mounted () {
			this.$nextTick(function () {
				setTimeout(() => {
					this.ready = this.dataId
				}, 100)
			})
		},
		methods: {
			//返回上一页
			back () {
				this.jump = 0
				this.$nextTick(function () {
					this.jump = -1
				})
			},
			//前进下一页
			forward () {
				this.jump = 0
				this.$nextTick(function () {
					this.jump = 1
				})
			},
			//刷新网页
			reload () {
				this.isReload = -1
				this.$nextTick(function () {
					this.isReload = 1
				})
			},
			//调用内部iframe跳转链接
			assign (url) {
				this.assignUrl = url
			},
			//显示进度条加载
			showLoading () {
				this.loadingStatus = -1
				this.$nextTick(function () {
					this.loadingStatus = 1
				})
			},
			//关闭进度条加载
			hideLoading () {
				this.loadingStatus = -1
				this.$nextTick(function () {
					this.loadingStatus = 0
				})
			},
			//销毁
			destroy () {
				this.isDestroy = false
				this.$nextTick(() => {
					this.isDestroy = true
				})
			},
			//注入js
			evalJS (str) {
				if ( this.evalJSString ) {//如果又正在进行注入的js任务
					const id = new Date().getTime().toString() + Math.round(Math.random() * 10000)
					this.evalJSTask.set(id, str)//加入等待队列
				} else {
					this.evalJSString = str
				}
			},
			//注入css
			evalCSS (str) {
				this.evalCSSString = ''
				this.$nextTick(() => {
					this.evalCSSString = str
				})
			},
			//设置数据
			setData (key, value) {
				this.setDataName = key
				this.setDataValue = ''
				this.$nextTick(() => {
					this.setDataValue = value
				})
			},
			//消息通知
			postMessage (data) {
				this.postMessageValue = ''
				this.$nextTick(() => {
					this.postMessageValue = data
				})
			},
			loadstart (e) {
				this.$emit('loadstart', e)
			},
			loaded (e) {
				this.$emit('loaded', e)
			},
			callError (e) {
				this.$emit('error', e)
			},
			jumpError (type) {
				this.$emit(type + 'error')
			},
			callDestroy () {
				this.$emit('destroyed')
			},
			overrideUrlLoading (e) {
				this.$emit('overrideurlloading', e)
			},
			message (e) {
				this.$emit('message', e)
			},
			//注入js回调
			callEvalJS (e) {
				this.evalJSString = ''
				if ( this.evalJSTask.size ) {//如果等待队列有值，并且当前没有正在执行的注入js任务
					const firstEntrie = this.evalJSTask.entries().next().value//获取等待队列中的第一个
					this.evalJS(firstEntrie[1])//执行任务
					this.evalJSTask.delete(firstEntrie[0])//删除等待队列
				}
				if ( e.status == 'error' ) this.callError(e.message)
			}
		}
	}
</script>
<!-- #ifdef APP-VUE || H5 -->
<script lang="renderjs" type="module" module="ComIframe">
	export default {
		data () {
			return {
				dom: null,
				iframe: null,
				loading: null,
				iframeSrc: '',
				jumpTimer: null,
				consoleTimer: null,
				currentUrl: ''
			}
		},
		mounted () {
			window.addEventListener('message', this.messageListener);
		},
		beforeDestroy () {
			this.destoryIframe()
		},
		methods: {
			messageListener (e) {
				this.callMethod('message', {origin: e.origin, data: e.data})
			},
			destoryIframe () {
				this.clearJumpTimer()
				this.clearConsoleTimer()
				window.removeEventListener('message', this.messageListener)
				if ( this.loading ) {
					this.loading.remove()
					this.loading = null
				}
				if ( this.iframe ) {
					this.iframe.remove()
					this.iframe = null
				}
				this.callMethod('callDestroy')
			},
			showLoadingRender () {
				if ( this.loading && this.getData('loadingShow') ) {
					this.loading.classList.remove('browser-loading-hide')
					this.loading.classList.add('browser-loading-show')
				}
			},
			hideLoadingRender () {
				if ( this.loading && this.getData('loadingShow') ) {
					this.loading.classList.remove('browser-loading-show')
					this.loading.classList.add('browser-loading-hide')
				}
			},
			clearJumpTimer () {
				if ( this.jumpTimer ) {
					window.clearTimeout(this.jumpTimer)
					this.jumpTimer = null
				}
			},
			clearConsoleTimer () {
				if ( this.consoleTimer ) {
					window.clearTimeout(this.consoleTimer)
					this.consoleTimer = null
				}
			},
			initIframe () {
				this.showLoadingRender()
				this.callMethod('loadstart', {href: this.iframeSrc})
				this.iframe = document.createElement('IFRAME')
				this.iframe.setAttribute('class', 'find_iframe_iframe')
				this.iframe.setAttribute('frameborder', this.getData('frameborder'))
				this.iframe.setAttribute('allow', this.getData('allow'))
				this.iframe.setAttribute('allowfullscreen', this.getData('allowfullscreen'))
				this.iframe.setAttribute('sandbox', this.getData('sandbox'))
				this.iframe.setAttribute('crossOrigin', this.getData('crossOrigin'))
				if ( this.getData('iframeStyle') ) this.iframeStyleWatcher(JSON.parse(this.getData('iframeStyle')))//初始化style
				if ( this.getData('iframeClass') ) this.iframeClassWatcher(JSON.parse(this.getData('iframeClass')))//初始化style
				this.iframe.src = this.iframeSrc
				this.iframe.onload = () => {
					this.hideLoadingRender()
					const iframeWindow = this.iframe.contentWindow
					iframeWindow.onbeforeunload = (e) => {
						// this.clearJumpTimer()
						this.clearConsoleTimer()
						this.callMethod('loadstart', e)
						this.showLoadingRender()
						// 当iframe内部发生跳转时，需要重新初始化console，延迟是为了等待页面开始加载，否则可能捕捉不到
						this.consoleTimer = window.setTimeout(() => {
							this.captureConsole()
						}, 100)
					}
					const iframeDocument = this.iframe.contentDocument || iframeWindow.document;
					const title = iframeDocument.title
					const href = iframeWindow.location.href
					const head = iframeDocument.head;
					const links = head.querySelectorAll('link[rel="icon"], link[rel="shortcut icon"]');//尝试从网页代码中获取favicon.ico
					const favicon = links.length > 0 ? links[0].href : href + (href.substring(href.length -1) == '/' ? '' : '/') + 'favicon.ico'//没有获取到默认使用网址拼接favicon.ico
					this.callMethod('loaded', {title, href, favicon})
				}
				this.iframe.onerror = (e) => {
					this.hideLoadingRender()
					this.callMethod('callError', e)
				}
				this.dom.appendChild(this.iframe)
				//加载拦截
				const options = JSON.parse(this.getData('overrideUrlLoadingOptions'))
				if ( options && options.mode ) this.overrideUrlLoadingWatcher(this.getData('overrideUrlLoadingOptions'))
				this.captureConsole()
			},
			//捕捉iframe内部console
			captureConsole () {
				if ( !this.iframe ) return
				// const iframeWindow = this.iframe.contentWindow
				// var _this = this
				// const consoles = this.getData('console').split(' ')
				// Object.keys(iframeWindow.console).forEach(key => {
				// 	iframeWindow.console[key] = function (...args) {
				// 		if ( consoles.indexOf(key) > -1 ) console[key](...args)
				// 	}
				// })
				// this.evalJSWatcher(`
				// 	console.log = function () {}
				// 	console.warn = function () {}
				// 	console.error = function () {}
				// 	console.info = function () {}
				// `)
			},
			readyWatcher (newVal) {
				if ( newVal ) {
					this.dom = document.querySelector('.iframe' + newVal)
					this.loading = document.createElement('DIV')
					this.loading.setAttribute('class', 'browser-loading-line')
					this.dom.appendChild(this.loading)
				}
				if ( this.iframeSrc && this.dom ) this.initIframe()
			},
			destroyWatcher (newVal) {
				if ( newVal ) this.destoryIframe()
			},
			srcWatcher (newVal, oldVal) {
				this.iframeSrc = this.parseSrc(newVal)
				if ( !this.dom ) return
				if ( newVal != oldVal ) {
					if ( oldVal ) this.destoryIframe()
					if ( newVal ) this.initIframe()
				}
			},
			sandboxWatcher (newVal) {
				this.iframe && this.iframe.setAttribute('sandbox', newVal)
			},
			allowWatcher (newVal) {
				this.iframe && this.iframe.setAttribute('allow', allow)
			},
			allowfullscreenWatcher (newVal) {
				this.iframe && this.iframe.setAttribute('allowfullscreen', newVal)
			},
			frameborderWatcher (newVal) {
				this.iframe && this.iframe.setAttribute('frameborder', newVal)
			},
			iframeStyleWatcher (newVal) {
				if ( typeof newVal == "string" ) this.iframe && this.iframe.setAttribute('style', newVal)
				if ( typeof newVal == "object" ) {
					let style = ''
					Object.keys(newVal).forEach(key => {
						style += key + ':' + newVal[key] + ';'
					})
					this.iframe && this.iframe.setAttribute('style', style)
				}
			},
			iframeClassWatcher (newVal) {
				if ( typeof newVal == "string" ) this.iframe && this.iframe.setAttribute('class', 'find_iframe_iframe ' + newVal)
				if ( typeof newVal == "object" ) {
					let className = ''
					Object.keys(newVal).forEach(key => {
						className += newVal[key] ? (key + ' ') : ''
					})
					this.iframe && this.iframe.setAttribute('class', 'find_iframe_iframe ' + className)
				}
			},
			jumpWatcher (newVal) {
				this.clearJumpTimer()
				if ( !this.iframe ) return
				//跳转前 记录当前链接
				this.currentUrl = this.iframe.contentWindow.location.href;
				if ( newVal < 0 ) {
					this.iframe.contentWindow.history.back()
					this.jumpTimer = window.setTimeout(() => {
						//判断当前iframe内的链接和跳转前记录的链接是否相同，相同代表跳转失败
						if ( this.currentUrl == this.iframe.contentWindow.location.href ) this.callMethod('jumpError', 'back')
					}, 100)
				}
				if ( newVal > 0 ) {
					this.iframe.contentWindow.history.forward()
					this.jumpTimer = window.setTimeout(() => {
						//判断当前iframe内的链接和跳转前记录的链接是否相同，相同代表跳转失败
						if ( this.currentUrl == this.iframe.contentWindow.location.href ) this.callMethod('jumpError', 'forward')
					}, 100)
				}
			},
			assignUrlWatcher (newVal) {
				const iframeWindow = this.iframe?.contentWindow
				iframeWindow && iframeWindow.window.location.assign(this.parseSrc(newVal));
			},
			loadingWatcher (newVal) {
				if ( newVal == 0 ) this.hideLoadingRender()
				if ( newVal == 1 ) this.showLoadingRender()
			},
			evalJSWatcher (newVal) {
				if ( newVal ) {
					try{
						const iframeDocument = this.iframe.contentDocument || iframeWindow.document;
						const script = iframeDocument.createElement('SCRIPT')
						script.innerHTML = newVal
						iframeDocument.head.appendChild(script)//插入script标签
						this.callMethod('callEvalJS', {status: 'success'})//通知逻辑层成功执行回调
						iframeDocument.head.removeChild(script)//执行完script标签后需要移除
					}catch(e){
						this.callMethod('callEvalJS', {status: 'error', message: JSON.stringify(e)})
					}
				}
			},
			evalCSSWatcher (newVal) {
				if ( newVal ) {
					try{
						const iframeDocument = this.iframe.contentDocument || iframeWindow.document;
						const style = iframeDocument.createElement('STYLE')
						style.innerHTML = newVal
						iframeDocument.head.appendChild(style)
					}catch(e){
						this.callMethod('callError', e)
					}
				}
			},
			//给网页值传参
			setDataWatcher (newVal) {
				if ( newVal ) {
					try{
						const iframeWindow = this.iframe.contentWindow
						const key = this.getData('setDataName')
						if ( key ) iframeWindow[key] = newVal
					}catch(e){
						this.callMethod('callEvalJS', {status: 'error', message: JSON.stringify(e)})
					}
				}
			},
			//向网页通信
			postMessageWatcher (newVal) {
				if ( newVal ) {
					try{
						const iframeWindow = this.iframe.contentWindow
						iframeWindow.postMessage(newVal)
					}catch(e){
						this.callMethod('callEvalJS', {status: 'error', message: JSON.stringify(e)})
					}
				}
			},
			//刷新网页
			reloadWatcher (newVal) {
				if ( newVal == -1 ) return
				const iframeWindow = this.iframe?.contentWindow
				iframeWindow && iframeWindow.window.location.reload()
			},
			//拦截监听
			overrideUrlLoadingWatcher (newVal) {
				// #ifdef APP-VUE
				const wv = plus.webview.currentWebview()
				const options = JSON.parse(newVal)
				wv.overrideUrlLoading(options, (e) => {
					this.callMethod('overrideUrlLoading', e)
					return true;
				});
				// #endif
				
			},
			getData (name) {
				const value = this.dom.getAttribute('data-' + name)
				if ( ['true', 'false'].includes(value) ) return value == 'false' ? false : true
				else if ( /^\d+$/.test(value) ) return Number(value)
				else return value
			},
			//处理播放链接（针对本地链接）
			parseSrc (src) {
				//本地static文件夹下的文件需要单独处理路径，否则无法访问
				if ( src.startsWith('/static') ) {
					// #ifdef H5
					return window.location.origin + window.location.pathname + src.substring(1)
					// #endif
					// #ifdef APP-PLUS
					return plus.io.convertLocalFileSystemURL(src)
					// #endif
				}
				return src
			},
			callMethod (name, args) {
				// #ifndef H5
				this.$ownerInstance.callMethod(name, args)
				// #endif
				// #ifdef H5
				this[name](args)
				// #endif
			}
		}
	}
</script>
<!-- #endif -->


<style>
	@keyframes loading-show {
		from {
			width: 0;
		}
		to {
			width: 99%;
		}
	}
	@keyframes loading-hide {
		from {
			width: 0;
			opacity: 1;
		}
		to {
			width: 100%;
			opacity: 0;
		}
	}
	.find_iframe {
		position: relative;
		width: 100%;
		height: 100%;
	}
	/deep/ .find_iframe_iframe {
		width: 100%;
		height: 100%;
	}
	/deep/ .browser-loading-line {
		position: absolute;
		top: 0;
		left: 0;
		width: 0;
		height: 3px;
		border-radius: 3px;
		background-color: #4cd964;
		z-index: 1;
		touch-action: none;
		pointer-events: none;
	}
	/deep/ .browser-loading-show {
		animation: loading-show 5s ease both;
	}
	/deep/ .browser-loading-hide {
		animation: loading-hide .5s linear both;
	}
</style>